package com.dexclassdemo.liuguangli.dexclassloaderdemo;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    private static final String ASSETS_PLUGINS_DIR = "des";
    private static final String OPT_DIR = "opt";

    private ClassLoader dexClassLoader;
    private Resources pluginResources;
    private Object packageInfo;

    private String installDir;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        File file = getDir("plugins", 0);
        installDir = file.getAbsolutePath();
        try {
            packageInfo = getPackageInfo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void jump(View view){
        loadDexClassses();
    }

    /**
     * ImportedFragment用lodDexClasses创建的AssetManager去加载资源。
     * @param view
     */
    public void startFragment(View view){
        Fragment fragment = null;
        try {
            Class<? extends Fragment> clazz = (Class<? extends Fragment>) dexClassLoader.loadClass("com.dexclassdemo.liuguangli.apkbeloaded.ImportedFragment");
            Object instance = clazz.newInstance();
            fragment = (Fragment) instance;
            Method method = clazz.getDeclaredMethod("setResource", Resources.class);
            method.invoke(fragment, pluginResources);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(fragment != null) {
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.add(R.id.fragment_container, fragment, fragment.getClass().getSimpleName());
            transaction.commit();
        }
    }

    public void checkResource(View view){
        try{
//            Class clazz = dexClassLoader.loadClass("com.dexclassdemo.liuguangli.apkbeloaded.R$string");
            Class clazz = dexClassLoader.loadClass("com.alipay.iap.android.userapp.R$string");
            Field field = clazz.getField("app_name");
            int resId = (int)field.get(null);
            String appName = pluginResources.getString(resId);
            System.out.println("appName:"+appName);
//            Object result = pluginResources.getLayout(0x40030001);
//            System.out.println(result);
        }catch(Exception e){
            Log.i("Loader", "error:"+Log.getStackTraceString(e));
        }
    }

    private void loadDexClassses() {
        int i = R.layout.content_main;
        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            Log.v("loadDexClassses", "LoadDexClasses is only available for ICS or up");
        }
        String paths[] = null;
        try {
            paths = getAssets().list("plugins");
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (paths == null) {
            Log.v("loadlDexClasses", "There was no " + paths);
            return;
        }

        Log.v("loadDexClasses", "Dex Preparing to loadDexClasses!");

        String file = paths[0];
//        String des = installDir + "/" + "apkbeloaded-debug.apk";
        String des = installDir + "/" + "chatapp_build_raw.apk";
        File desFile = new File(des);
        File optimizedDirectory = this.getDir(OPT_DIR, Context.MODE_PRIVATE);
        if (!desFile.exists()){
            copyAssetsApkToFile(this, "plugins/" + file, des);
        }
        try {
            dexClassLoader = new DexClassLoader(
                    des, optimizedDirectory.getAbsolutePath(),
                    "data/local/tmp/natives/",
                    getClassLoader());
            initPluginResource(des);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initPluginResource(String apkPath) throws Exception {
        /**
         * AssetManager会根据AssetPath依次去读取资源
         */
        AssetManager assetManager = AssetManager.class.newInstance();
        Method method = AssetManager.class.getDeclaredMethod(
                "addAssetPath", new Class<?>[] { String.class });
        method.setAccessible(true);
        method.invoke(assetManager, getApplicationInfo().sourceDir);
//        method.invoke(assetManager, apkPath);
        Resources resources = new Resources(assetManager, getResources().getDisplayMetrics(), getResources().getConfiguration());
//        Field mResourcesField = packageInfo.getClass().getDeclaredField(
//                "mResources");
//        mResourcesField.setAccessible(true);
//        mResourcesField.set(packageInfo, resources);
        pluginResources = resources;
    }

    public void copyAssetsApkToFile(Context context, String src, String des) {
        try {
            InputStream is = context.getAssets().open(src);
            FileOutputStream fos = new FileOutputStream(new File(des));
            byte[] buffer = new byte[1024];
            while (true) {
                int len = is.read(buffer);
                if (len == -1) {
                    break;
                }
                fos.write(buffer, 0, len);
            }
            is.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object getPackageInfo() throws NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        Context contextImpl = getBaseContext();
        Field mPackageInfoField = contextImpl.getClass().getDeclaredField(
                "mPackageInfo");
        mPackageInfoField.setAccessible(true);
        Object mPackageInfo = mPackageInfoField.get(contextImpl);
        return mPackageInfo;
    }

    public void test() throws Exception {
        Class<?> classToLoad = (Class<?>) dexClassLoader.loadClass("com.dexclassdemo.liuguangli.apkbeloaded.Registry");
        Field classesField = classToLoad.getDeclaredField("_classes");
        ArrayList<Class<?>> classes = null;
        classes = (ArrayList<Class<?>>) classesField.get(null);
        for (Class<?> cls : classes) {
            Log.v("loadDexClasses", "Class loaded " + cls.getName());
            if (cls.getName().contains("ClassToBeImported")) {
                Method m = cls.getMethod("method");
                ClassLoader xb = (ClassLoader) m.invoke(null);
                if (xb.equals(ClassLoader.getSystemClassLoader()))
                    Log.v("loadDexClasses", "Same ClassLoader");
                else
                    Log.v("loadDexClasses", "Different ClassLoader");
                Log.v("loadDexClasses", xb.toString());
            }
        }
    }

}
